package com.suray.wcs.service.core.util;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.suray.basic.wcs.node.WCSNodeModule;
import com.suray.basic.wcs.node.bo.WCSNodeModuleBO;
import com.suray.basic.wcs.node.bo.WCSNodeModuleRouteBO;
import com.suray.basic.wcs.path.core.Lifter;
import com.suray.basic.wcs.path.core.Node;
import com.suray.basic.wcs.path.properties.PathProperties;
import com.suray.basic.wcs.plc.core.Plc;
import com.suray.basic.wcs.utils.Coord;
import com.suray.basic.wcs.utils.LockNode;
import com.suray.commin.core.utils.SpringContextHolder;
import com.suray.wcs.res.enums.RgvStatus;
import com.suray.wcs.service.enums.PlusType;
import com.suray.wcs.service.pojo.Rgv;
import com.suray.wcs.service.system.entity.NodeDB;
import com.suray.wcs.service.system.entity.TaskDB;
import com.suray.wcs.service.system.service.INodeDBService;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * @author Shaozn
 */
public class LockNodeManager {
    private static volatile Set<LockNode> lockedNodes;
    private static volatile Set<LockNode> deviceLockNodes;
    private static Logger log = LoggerFactory.getLogger(LockNodeManager.class);
    private static final INodeDBService nodeDBService = SpringContextHolder.getBean(INodeDBService.class);

    static {
        lockedNodes = Sets.newConcurrentHashSet();
        deviceLockNodes = Sets.newConcurrentHashSet();
    }

    /**
     * 获取未加锁的点
     *
     * @param nodeSet 待检查节点
     * @return 被obj占用则返回true, 否则返回false
     */
    public static synchronized Set<Coord> getUnLockedNode(Set<Coord> nodeSet) {
        Iterator<Coord> it = nodeSet.iterator();
        while (it.hasNext()) {
            Coord coord = it.next();
            if (lockedNodes.contains(coord)) {
                it.remove();
            }
        }
        return nodeSet;
    }


    /**
     * 获取当前被锁定的节点对象
     *
     * @return LockNode对象
     */
    public static Set<LockNode> getLockedNodes() {
        return lockedNodes;
    }

    /**
     * 获取当前被锁定的节点对象,除了被自己锁定的点
     *
     * @return LockNode对象
     */
    public static Set<LockNode> getLockedNodesExceptSelf(Rgv rgv) {
        Set<LockNode> lockNodeSet = Sets.newHashSet();
        for (LockNode lockNode : lockedNodes) {
            if (!lockNode.getLockedBy().equals(rgv)) {
                lockNodeSet.add(lockNode);
            }
        }
        return lockNodeSet;
    }

    /**
     * 检查该节点集合nodeSet是否被该对象obj锁定
     *
     * @param nodeSet 待检查节点集合
     * @return 被obj占用则返回true, 否则返回false
     */
    public static synchronized <T> boolean isLockedByObj(Set<Coord> nodeSet, T t) {
        boolean isLocked = false;
        for (LockNode lockNode : lockedNodes) {
            for (Coord node : nodeSet) {
                if (lockNode.equals(node) && lockNode.getLockedBy().equals(t)) {
                    isLocked = true;
                    break;
                }
            }
        }
        return isLocked;
    }

    /**
     * 检查该节点集合nodeSet是否被该对象obj锁定
     *
     * @param nodeSet 待检查节点集合
     * @param <T>     被占用则返回 未被占用返回空
     * @return
     */
    public static synchronized <T> T isLocked(Set<Coord> nodeSet) {
        for (LockNode lockNode : lockedNodes) {
            for (Coord node : nodeSet) {
                if (lockNode.equals(node)) {
                    return (T) lockNode.getLockedBy();
                }
            }
        }
        return null;
    }


    /**
     * 直接解锁特定的节点
     *
     * @param node
     */
    public static synchronized void removeLockedNode(Coord node) {
        lockedNodes.remove(node);
    }

    /**
     * 添加进锁定节点集合 会对资源占用进行校验, 若已被自己占用则忽略, 被其他RGV占用则抛出异常
     *
     * @param nodeSet
     */
    public static synchronized void addLockedNodes(Set<Coord> nodeSet, Rgv rgv) {
        updateLockedNodes(nodeSet, true, rgv);
    }

    /**
     * 将占用资源释放 若已被占用且为自己占用则释放, 若为其他RGV占用或未被占用则忽略
     *
     * @param nodeSet
     */
    public static synchronized <T> void removeLockedNodes(Set<Coord> nodeSet, T t) {
        updateLockedNodes(nodeSet, false, t);
    }

    /**
     * 检查待检查节点集合是否存在被占用的节点资源
     *
     * @param nodeSet 待检查节点集合
     * @return 被占用则返回true, 否则返回false
     */
    public static synchronized <T> boolean checkNodesLocked(Set<Coord> nodeSet, T t) {
        for (LockNode lockNode : lockedNodes) {
            for (Coord node : nodeSet) {
                if (lockNode.equals(node) && !lockNode.getLockedBy().equals(t)) {
                    log.info("节点" + node + "被" + lockNode.getLockedBy() + "占用");
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 检查该节点是否是占用状态
     *
     * @param node 需要检查的节点
     * @return 是否被占用
     */
    public static synchronized boolean checkNodesLocked(Coord node) {
        for (LockNode lockNode : lockedNodes) {
            if (lockNode.equals(node)) {
                log.info("节点" + node + "被" + lockNode.getLockedBy() + "占用");
                return true;
            }
        }
        return false;
    }

    /**
     * 检查待检查节点集合是否存在被占用的节点资源
     *
     * @param nodes 待检查节点集合
     * @return 被自己占用则返回null, 否则返回占用者
     */
    public static synchronized <T> T checkNodeListLockBy(List<Node> nodes, T t) {
        for (LockNode lockNode : lockedNodes) {
            for (Coord node : nodes) {
                if (lockNode.equals(node) && !lockNode.getLockedBy().equals(t)) {
                    log.info("节点" + node + "被" + lockNode.getLockedBy() + "占用");
                    return (T) lockNode.getLockedBy();
                }
            }
        }
        return null;
    }

    /**
     * 获取该小车待行驶的路径里的节点被占用的小车集合
     *
     * @param curRgv
     * @return
     */
    public static List<Rgv> getNextRoadRejectRgvList(Rgv curRgv) {
        List<Rgv> rejectRgvSet = new ArrayList<>();
        if (curRgv.getWaitExecuteNodeList() == null || curRgv.getWaitExecuteNodeList().isEmpty()) {
            //该小车无待行驶道路
            return rejectRgvSet;
        }
        List<Node> next = getNextNode(curRgv.getWaitExecuteNodeList());
        List<LockNode> lockNodeList = new ArrayList<>();
        //找出已被占用的节点
        for (LockNode node : lockedNodes) {
            if (next.contains(node)) {
                lockNodeList.add(node);
            }
        }
        //找出被占用节点的占用者
        for (LockNode lockNode : lockNodeList) {
            if (!lockNode.getLockedBy().equals(curRgv) && lockNode.getLockedBy() instanceof Rgv) {
                rejectRgvSet.add((Rgv) lockNode.getLockedBy());
            }
        }
        return rejectRgvSet;
    }

    private static List<Node> getNextNode(List<Node> waitExecuteNodeList) {
        List<Node> next = Lists.newArrayList();
        for (Node node : waitExecuteNodeList) {
            next.addAll(node.getLockedNodeSet());
        }
        return next;
    }

    /**
     * 返回curRgv前方的排斥车辆
     *
     * @param curRgv
     * @return
     */
    public static Rgv getFontRejectRgv(Rgv curRgv) {
        if (curRgv.getWaitExecuteNodeList() == null || curRgv.getWaitExecuteNodeList().isEmpty()) {
            //该小车无待行驶道路
            return null;
        }
        List<Node> next = getNextNode(curRgv.getWaitExecuteNodeList());
        //找出第一个已被占用的节点
        for (Coord pathCoord : next) {
            for (LockNode node : lockedNodes) {
                if (pathCoord.equals(node)) {
                    if (node.getLockedBy() != null && node.getLockedBy() instanceof Rgv) {
                        Rgv rgv = (Rgv) node.getLockedBy();
                        if (!rgv.equals(curRgv)) {
                            return rgv;
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 更新位置资源锁(从当前线程获取Rgv对象)
     */
    public static synchronized void updateLocationLock(Rgv rgv) {
        if (rgv == null || rgv.getRgvDB().getLocation() == null) {
            return;
        }
        Node node = rgv.getRgvDB().getLocationNode();
        node.passTime = 10000;
        Set<Coord> locationNode = Collections.singleton(node);
        if (!isLockedByObj(locationNode, rgv)) {
            log.info("更新" + rgv.getRgvName() + "坐标为" + rgv.getRgvDB().getLocationNode().toString());
            addLockedNodes(locationNode, rgv);
            if (rgv.getRgvStatus() == RgvStatus.OFF_LINE) {
                updateLockedNodeUnLockTime(Collections.singletonList(node), rgv);
            }
        }
        lockOtherNode(node, rgv);
    }

    public static synchronized <T> void lockNode(Node node, T t) {
        Set<Coord> locationNode = Collections.singleton(node);
        if (!isLockedByObj(locationNode, t)) {
            updateLockedNodes(locationNode, true, t);
            updateLockedNodeUnLockTime(Collections.singletonList(node), t);
        }
//        lockOtherNode(node, obj);
    }

    /**
     * 锁定相邻点
     *
     * @param node 当前点
     * @param rgv  当前车辆
     */
    private static void lockOtherNode(Node node, Rgv rgv) {
        if (rgv.getCurTask() == null && node != null) {
            Set<Node> nodes = needLockNode(node);
            addLockedNodes(getUnLockedNode(new HashSet<>(nodes)), rgv);
            updateLockedNodeUnLockTime(Collections.singletonList(node), rgv);
        }
    }

    /**
     * 小车如果在该位置需要锁的节点
     *
     * @param node 当前点
     */
    public static Set<Node> needLockNode(Node node) {
        if (node != null) {
            Set<Node> nodeSet = Sets.newHashSet();
            // 获取nodeDB
            NodeDB nodeDB = nodeDBService.getNodeDB(node);
            if (nodeDBService.checkPlusNodeDB(nodeDB) && nodeDB.getCanToUp() && PathProperties.LOCK_UP_COUNT != 0) {
                nodeDBService.addPlusNode(node, nodeSet, PlusType.UP, PathProperties.LOCK_UP_COUNT);
            }
            if (nodeDBService.checkPlusNodeDB(nodeDB) && nodeDB.getCanToDown() && PathProperties.LOCK_DOWN_COUNT != 0) {
                nodeDBService.addPlusNode(node, nodeSet, PlusType.DOWN, PathProperties.LOCK_DOWN_COUNT);
            }
            if (nodeDBService.checkPlusNodeDB(nodeDB) && nodeDB.getCanToLeft() && PathProperties.LOCK_LEFT_COUNT != 0) {
                nodeDBService.addPlusNode(node, nodeSet, PlusType.LEFT, PathProperties.LOCK_LEFT_COUNT);
            }
            if (nodeDBService.checkPlusNodeDB(nodeDB) && nodeDB.getCanToRight() && PathProperties.LOCK_RIGHT_COUNT != 0) {
                nodeDBService.addPlusNode(node, nodeSet, PlusType.RIGHT, PathProperties.LOCK_RIGHT_COUNT);
            }
            return nodeSet;
        }
        return null;
    }

    public static synchronized <T> Set<Coord> getLockCoordByObj(T t) {
        Set<Coord> lockedByObj = new HashSet<>();
        Iterator<LockNode> iterator = lockedNodes.iterator();
        while (iterator.hasNext()) {
            LockNode next = iterator.next();
            if (next.getLockedBy().equals(t)) {
                lockedByObj.add(new Coord(next));
            }
        }
        return lockedByObj;
    }

    /**
     * 锁定提升机
     *
     * @param lifter
     */
    public static <T> void lockLifter(Lifter lifter, T t) {
        Coord lifterCoord = Plc.lifterCoordMap.get(Plc.deviceLifterCoordMap.get(new Coord(lifter.getX(), lifter.getY(), 1)).getDeviceName());
        synchronized (lifterCoord) {
            while (LockNodeManager.checkLifterLocked(lifter, t)) {
                if (t instanceof TaskDB) {
                    TaskDB taskDB = (TaskDB) t;
                    Plc.checkPLCInterrupt(taskDB.getTaskId().toString(), "LockNodeManager1");
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            updateLifterLock(lifter, true, t);
            log.info("占用提升机： " + lifter + "资源");
        }
    }

    /**
     * 锁定提升机
     *
     * @param lifter
     * @param t
     * @return
     */
    public static <T> boolean lockLifterSafe(Lifter lifter, T t) {
        Coord lifterCoord = Plc.lifterCoordMap.get(Plc.deviceLifterCoordMap.get(new Coord(lifter.getX(), lifter.getY(), 1)).getDeviceName());
        synchronized (lifterCoord) {
            if (LockNodeManager.checkLifterLocked(lifter, t)) {
                return false;
            }
            updateLifterLock(lifter, true, t);
            log.info("占用提升机： " + lifter + "资源");

            return true;
        }
    }

    /**
     * 解锁提升机
     *
     * @param lifter
     */
    public static synchronized <T> void unlockLifter(Lifter lifter, T t) {
        updateLifterLock(lifter, false, t);
        log.info("解锁提升机： " + lifter + "资源");
    }

    /**
     * 检查提升机资源是否被占用
     *
     * @param lifter
     * @return
     */
    public static synchronized <T> boolean checkLifterLocked(Lifter lifter, T t) {
        Set<Coord> lifterNodes = new HashSet<>();
        for (int i = 1; i <= 15; i++) {
            lifterNodes.add(new Coord(lifter.getX(), lifter.getY(), i));
        }
        return checkNodesLocked(lifterNodes, t);
    }

    /**
     * 锁定或释放提升机节点资源
     *
     * @param lifter
     */
    private static synchronized <T> void updateLifterLock(Lifter lifter, boolean addOrRemove, T t) {
        Set<Coord> lifterNodes = new HashSet<>();
        for (int i = 1; i <= 15; i++) {
            lifterNodes.add(new Coord(lifter.getX(), lifter.getY(), i));
        }
        updateLockedNodes(lifterNodes, addOrRemove, t);
    }

    /**
     * 更新资源占用的节点集合
     *
     * @param nodeSet     节点结合
     * @param addOrRemove 添加为true, 删除为false
     */
    private static synchronized <T> void updateLockedNodes(Set<Coord> nodeSet, boolean addOrRemove, T t) {
        List<LockNode> lockList = new ArrayList<>();
        if (addOrRemove) {
            for (Coord node : nodeSet) {
                boolean exist = false;
                for (LockNode lockNode : lockedNodes) {
                    if (lockNode.equals(node)) {
                        exist = true;
                        if (!lockNode.getLockedBy().equals(t)) {
                            log.error("占用失败, 该节点已被Rgv_" + lockNode.getLockedBy() + "占用");
                            throw new IllegalArgumentException(t
                                    + "占用失败, 该节点已被Rgv_" + lockNode.getLockedBy() + "占用");
                        }
                    }
                }
                if (!exist) {
                    lockedNodes.add(new LockNode<>(node, t));
                    lockList.add(new LockNode(node, t));
                }
            }
            if (!lockList.isEmpty()) {
                StringBuilder stringBuilder = new StringBuilder(t.toString() + "锁定以下点:");
                lockList.forEach(lockNode -> stringBuilder.append(lockNode.toString()));
                log.info(stringBuilder.toString());
            }
        } else {
            List<LockNode> removeList = new ArrayList<>();
            for (Coord node : nodeSet) {
                for (LockNode lockNode : lockedNodes) {
                    if (lockNode.equals(node) && lockNode.getLockedBy().equals(t)) {
                        // 设置改节点临时占用解除
                        removeList.add(lockNode);
                    }
                }
            }
            lockedNodes.removeAll(removeList);
            if (!removeList.isEmpty()) {
                StringBuilder stringBuilder = new StringBuilder(t.toString() + "解锁以下点:");
                removeList.forEach(lockNode -> stringBuilder.append(lockNode.toString()));
                log.info(stringBuilder.toString());
            }
        }
    }

    public static <T> void updateLockedNodeUnLockTime(List<Node> nodes, T t) {
        for (Node node : nodes) {
            if (lockedNodes.contains(node)) {
                for (LockNode lockNode : lockedNodes) {
                    if (lockNode.getLockedBy().equals(t) && node.getX() == lockNode.getX() && node.getY() == lockNode.getY() && node.getZ() == lockNode.getZ()) {
                        lockNode.setUnLockTime(node.passTime);
                        if (t instanceof Rgv) {
                            WCSNodeModuleBO wcsNodeModuleBO = WCSNodeModule.wcsNodeModuleMapCache.get(node.toString());
                            if (wcsNodeModuleBO != null) {
                                WCSNodeModuleRouteBO wcsNodeModuleRouteBO = wcsNodeModuleBO.getRouteBy().get(((Rgv) t).getRgvDB().getRgvNo().toString());
                                if (wcsNodeModuleRouteBO != null) {
                                    wcsNodeModuleRouteBO.setPassTime(node.passTime);
                                }
                            }
                            Set<Coord> linkLockedSet = nodeDBService.getLinkedNode(new Node(lockNode.getX(), lockNode.getY(), lockNode.getZ()));
                            linkLockedSet.removeAll(nodes);
                            if (CollectionUtils.isNotEmpty(linkLockedSet)) {
                                for (Coord coord : linkLockedSet) {
                                    if (lockedNodes.contains(coord)) {
                                        for (LockNode linkLockNode : lockedNodes) {
                                            if (linkLockNode.getLockedBy().equals(t) && coord.getX() == linkLockNode.getX() && coord.getY() == linkLockNode.getY() && coord.getZ() == linkLockNode.getZ()) {
                                                linkLockNode.setUnLockTime(node.passTime);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        break;
                    }
                }
            }
        }
    }

    public static <T> void updateLockedNodeUnLockTimeIncrement(Rgv rgv, Integer unlockTime) {
        for (LockNode lockNode : lockedNodes) {
            if (lockNode.getLockedBy().equals(rgv)) {
                lockNode.setUnLockTime(lockNode.getUnLockTime() + unlockTime);
                WCSNodeModuleBO wcsNodeModuleBO = WCSNodeModule.wcsNodeModuleMapCache.get(lockNode.toString().trim());
                if (wcsNodeModuleBO != null) {
                    WCSNodeModuleRouteBO wcsNodeModuleRouteBO = wcsNodeModuleBO.getRouteBy().get(rgv.getRgvDB().getRgvNo().toString());
                    if (wcsNodeModuleRouteBO != null) {
                        wcsNodeModuleRouteBO.setLockTime(0);
                        wcsNodeModuleRouteBO.setPassTime(wcsNodeModuleRouteBO.getPassTime() + unlockTime);
                    } else {
                        wcsNodeModuleRouteBO = new WCSNodeModuleRouteBO();
                        wcsNodeModuleRouteBO.setKey(rgv.getRgvDB().getRgvNo());
                        wcsNodeModuleRouteBO.setLockTime(0);
                        wcsNodeModuleRouteBO.setPassTime(10000);
                        wcsNodeModuleRouteBO.setCurrentPassTime(0);
                        wcsNodeModuleBO.addRouteBy(rgv.getRgvDB().getRgvNo().toString(), wcsNodeModuleRouteBO);
                    }
                }
            }
        }
    }

    public static void updateLockedNodeUnLockTimeForNoTask(Rgv rgv) {
        for (LockNode lockNode : lockedNodes) {
            if (lockNode.getLockedBy().equals(rgv)) {
                lockNode.setUnLockTime(1000L);
                WCSNodeModuleBO wcsNodeModuleBO = WCSNodeModule.wcsNodeModuleMapCache.get(lockNode.toString().trim());
                if (wcsNodeModuleBO != null) {
                    WCSNodeModuleRouteBO wcsNodeModuleRouteBO = wcsNodeModuleBO.getRouteBy().get(rgv.getRgvDB().getRgvNo().toString());
                    if (wcsNodeModuleRouteBO != null) {
                        wcsNodeModuleRouteBO.setLockTime(0);
                        wcsNodeModuleRouteBO.setPassTime(1000);
                    } else {
                        wcsNodeModuleRouteBO = new WCSNodeModuleRouteBO();
                        wcsNodeModuleRouteBO.setKey(rgv.getRgvDB().getRgvNo());
                        wcsNodeModuleRouteBO.setLockTime(0);
                        wcsNodeModuleRouteBO.setPassTime(1000);
                        wcsNodeModuleRouteBO.setCurrentPassTime(0);
                        wcsNodeModuleBO.addRouteBy(rgv.getRgvDB().getRgvNo().toString(), wcsNodeModuleRouteBO);
                    }
                }
            }
        }
    }

    public static synchronized <T> void lockedNodes(Set<Coord> nodeSet, T t) {
        updateLockedNodes(nodeSet, true, t);
    }

    public static synchronized <T> void unlockedNodes(Set<Coord> nodeSet, T t) {
        updateLockedNodes(nodeSet, false, t);
    }

    public static synchronized <T> boolean lockedNodesSafe(Set<Coord> nodeSet, T t) {
        if (isLocked(nodeSet) != null && !isLockedByObj(nodeSet, t)) {
            return false;
        }

        lockedNodes(nodeSet, t);
        return true;
    }

    public static synchronized boolean getDeviceLockSafe(Object obj, Set<Coord> nodeSet) {
        for (Coord coord : nodeSet) {
            if (checkDeviceLock(obj, coord) != null) {
                return false;
            }
        }

        for (Coord coord : nodeSet) {
            deviceLockNodes.add(new LockNode(coord, obj));
        }

        return true;
    }

    /**
     * 如果被锁定且锁定对象不是obj，返回锁定对象，否则返回null
     * @param obj
     * @param coord
     * @return
     */
    private static Object checkDeviceLock(Object obj, Coord coord) {
        for (LockNode deviceLockNode : deviceLockNodes) {
            if (deviceLockNode.equals(coord)) {
                if (!deviceLockNode.getLockedBy().equals(obj)) {
                    return deviceLockNode.getLockedBy();
                } else {
                    return null;
                }
            }
        }

        return null;
    }

    public static Object isDeviceLocked(Set<Coord> nodeSet) {
        for (LockNode lockNode : deviceLockNodes) {
            for (Coord node : nodeSet) {
                if (lockNode.equals(node)) {
                    return lockNode.getLockedBy();
                }
            }
        }
        return null;
    }

    /**
     * 解锁设备
     * @param object
     */
    public synchronized static void releaseDeviceLock(Object object) {
        Iterator<LockNode> iterator = deviceLockNodes.iterator();
        while (iterator.hasNext()) {
            if (iterator.next().getLockedBy().equals(object)) {
                iterator.remove();
            }
        }
    }
}
